//
// Created by 郝羽 on 2018/2/5.
//

#ifndef RTC_XSWITCH2_AUDIO_MIXER_HPP
#define RTC_XSWITCH2_AUDIO_MIXER_HPP

#include <iomanip>
#include <opus.h>
#include <cmath>
#include "input.hpp"
#include "output.hpp"

class MixerRoute {
public:
    std::string file_name;
    Input* in;
    Output* out;
    bool is_mix;
    bool is_reset_encoder;
    double gain;
    double energy;
    int    level;
    AVFrame* frame;
    OpusEncoder* encoder;
    OpusDecoder* decoder;
};

class MixerFrame {
public:
    AVFrame* frame;
    MixerRoute* route;
    double energy;
    int level;
};

class Mixer {
    const int mix_max = 2;
    const int ptime = 10;
    const int sample_rate = 16000;
    const int min_level = -60;
public:
    int Mix(const std::list<std::string>& input_file_names) {
        std::list<MixerRoute*> routes;
        for (auto filename : input_file_names) {
            // open input
            auto in = new Input();
            if (in->Open(filename, ptime)) {
                return -1;
            }

            // open output
            auto out = new Output();
            auto dot = filename.rfind(".");
            std::string out_file_name(filename.substr(0, dot));
            out_file_name.append("-out.");
            out_file_name.append(filename.substr(dot+1));
            if (out->Open(out_file_name, "wav", "pcm_s16le")) {
                return -1;
            }
            auto r = new MixerRoute();
            r->file_name = filename;
            r->in = in;
            r->out = out;
            r->is_mix = false;
            r->is_reset_encoder = false;
            r->gain = 0.0f;
            int err;
            r->encoder = opus_encoder_create(sample_rate, 1, OPUS_APPLICATION_VOIP, &err);
            if (err) {
                std::clog << "could not create encoder" << std::string(opus_strerror(err)) << std::endl;
                return -1;
            }
            opus_encoder_ctl(r->encoder, OPUS_SET_PREDICTION_DISABLED(1));
//            err = opus_encoder_ctl(r->encoder, OPUS_SET_COMPLEXITY(1));
//            if (err) {
//                std::clog << "could not set encoder complexity: " << std::string(opus_strerror(err)) << std::endl;
//                return -1;
//            }

            r->decoder = opus_decoder_create(sample_rate, 1, &err);
            if (err) {
                std::clog << "could not create decoder" << std::string(opus_strerror(err)) << std::endl;
                return -1;
            }

            routes.emplace_back(r);
        }

        AVFrame* frame_out = av_frame_alloc();
        frame_out->nb_samples = sample_rate / 1000 * ptime;
        frame_out->sample_rate = sample_rate;
        frame_out->format = AV_SAMPLE_FMT_S16;
        frame_out->channels = 1;
        frame_out->channel_layout = AV_CH_LAYOUT_MONO;
        av_frame_get_buffer(frame_out, 1);

        int err = 0;
        OpusEncoder* shared_encoder = opus_encoder_create(sample_rate, 1, OPUS_APPLICATION_VOIP, &err);
        if (err) {
            std::clog << "could not create main encoder" << std::string(opus_strerror(err)) << std::endl;
            return -1;
        }

//        err = opus_encoder_ctl(shared_encoder, OPUS_SET_COMPLEXITY(1));
//        if (err) {
//            std::clog << "could not set encoder complexity: " << std::string(opus_strerror(err)) << std::endl;
//            return -1;
//        }

        unsigned char shared_encoded_buf[512] = {0,};

        int64_t msec = 0;
        while (true) {
            msec += ptime;
            std::clog << std::setw(8) << (float) msec / 1000 << " ";
            std::list<MixerFrame> frames;

            for (auto r : routes) {
                r->frame = r->in->Read();
                if (r->frame == nullptr) {
                    continue;
                }

                double e = 0;
                int16_t* v = (int16_t*)(r->frame->data[0]);
                for (auto j = 0; j < r->frame->nb_samples; j++) {
                    double dv = (double)*v / 32767;
                    e += (dv * dv);
                    v += 1;
                }
                e /= r->frame->nb_samples;
                r->energy = e;
                r->level = 10 * std::log10(r->energy);
                frames.emplace_back(MixerFrame{r->frame, r, r->energy, r->level});

            }

            if (frames.empty()) {
                break;
            }

            frames.sort([](const MixerFrame& f1, const MixerFrame& f2) {
                if (f1.level > f2.level && f1.energy < f2.energy) {
                    std::clog << "should not happen!!!" << std::endl;
                }
                return f1.level > f2.level;
            });

            memset(frame_out->data[0], 0, frame_out->linesize[0]);
            int n = 0;
            bool is_reset_main_encoder = false;
            std::clog << "speakers: ";
            for (auto f : frames) {
                n += 1;
                if (n > mix_max || f.level < min_level) {
                    if (f.route->is_mix) {
                        is_reset_main_encoder = true;
                        auto data = (int16_t*)f.frame->data[0];
                        for (int i = 0; i < f.frame->nb_samples; i++) {
                            double gain = 1.0 / f.frame->nb_samples * (f.frame->nb_samples - i);
                            *data *= gain;
                        }

                        auto data_in = (int16_t*)f.frame->data[0];
                        auto data_out = (int16_t*)frame_out->data[0];
                        for (auto i = 0; i < f.frame->nb_samples; i++) {
                            *data_out += *data_in;
                            data_in += 1;
                            data_out += 1;
                        }

                    }
                    f.route->is_mix = false;
                    std::clog << "-" << f.route->file_name << " (" << f.level << ") ";
                    continue;
                }

                if (!f.route->is_mix) {
                    f.route->is_reset_encoder = true;
                    auto data = (int16_t*)f.frame->data[0];
                    for (int i = 0; i < f.frame->nb_samples; i++) {
                        double gain = 1.0 / f.frame->nb_samples * i;
                        *data *= gain;
                    }
                }

                auto data_in = (int16_t*)f.frame->data[0];
                auto data_out = (int16_t*)frame_out->data[0];

                for (auto i = 0; i < f.frame->nb_samples; i++) {
                    *data_out += *data_in;
                    data_in += 1;
                    data_out += 1;
                }

                f.route->is_mix = true;

                std::clog << "+" << f.route->file_name << "(" << f.level << ") ";
            }

//            if (is_reset_main_encoder) {
//                opus_encoder_ctl(shared_encoder, OPUS_RESET_STATE);
//                is_reset_main_encoder = false;
//            }

            int shared_encoded_len = opus_encode(shared_encoder, (int16_t*)frame_out->data[0], frame_out->nb_samples, shared_encoded_buf, sizeof(shared_encoded_buf));
            std::clog << std::endl;

            for (auto r : routes) {
                if (r->is_reset_encoder) {
                    // reset better than feed silence, the latter create distortion
                    //
                    opus_encoder_ctl(r->encoder, OPUS_RESET_STATE);
//                    int16_t zero[1280] = {0,};
//                    unsigned char dummy[320];
//                    opus_encode(r->encoder, zero, r->frame->nb_samples, dummy, 320);
                    r->is_reset_encoder = false;
                }
                if (r->is_mix) {
                    unsigned char encoded_buf[512] = {0,};

                    auto data_in = (int16_t*)(frame_out->data[0]);
                    auto data_out = (int16_t*)(r->frame->data[0]);
                    for (auto i = 0; i < r->frame->nb_samples; i++) {
                        *data_out = *data_in - *data_out;
                        data_in += 1;
                        data_out += 1;
                    }

                    int n = opus_encode(r->encoder, (int16_t*)r->frame->data[0], r->frame->nb_samples, encoded_buf, sizeof(encoded_buf));
                    int d = opus_decode(r->decoder, encoded_buf, n, (int16_t*)r->frame->data[0], r->frame->nb_samples, 0);
                    r->out->Write(r->frame);
                } else {
                    int d = opus_decode(r->decoder, shared_encoded_buf, shared_encoded_len, (int16_t*)r->frame->data[0], r->frame->nb_samples, 0);
                    r->out->Write(r->frame);
                    //r->out->Write(frame_out);
                }
            }
        }

        for (auto r : routes) {
            r->in->Close();
            r->out->Close();
        }
    }
};

#endif //RTC_XSWITCH2_AUDIO_MIXER_HPP
